use std::{convert::Infallible, time::Duration};

use axum::{
    http::HeaderValue,
    response::{sse::Event, Sse},
    routing::{get, post},
    Router,
};
use axum_extra::TypedHeader;
use futures::{stream, Stream};
use tokio::net::TcpListener;
use tokio_stream::StreamExt;
use tower_http::{
    cors::{Any, CorsLayer},
    trace::TraceLayer,
};

#[tokio::main]
async fn main() {
    println!("Hello, app lf-api!");

    let app = Router::new()
        .route("/", get(default_handler))
        .nest(
            "/sse",
            Router::new()
                .route("/", post(sse_handler))
                .route("/:id", post(sse_handler_with_id)),
        )
        .nest(
            "/api",
            Router::new()
                .route("/", get(api_handler))
                .route("/:id", get(api_handler_with_id)),
        )
        .layer(
            // see https://docs.rs/tower-http/latest/tower_http/cors/index.html
            // for more details
            //
            // pay attention that for some request types like posting content-type: application/json
            // it is required to add ".allow_headers([http::header::CONTENT_TYPE])"
            // or see this issue https://github.com/tokio-rs/axum/issues/849
            CorsLayer::new()
                .allow_origin("http://localhost:3000".parse::<HeaderValue>().unwrap())
                .allow_methods(Any)
                .allow_headers(Any),
        )
        .layer(TraceLayer::new_for_http());

    let listener = TcpListener::bind("127.0.0.1:30000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn default_handler() -> String {
    "Hello, lf-api default_handler!".to_string()
}

async fn sse_handler(
    TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
    println!("`{}` connected", user_agent.as_str());

    let stream = stream::repeat_with(|| Event::default().data(format!("\"{}\\\"msg\\\":\\\"OK\\\",\\\"code\\\":0{}\"", "{", "}")))
        .map(Ok)
        .throttle(Duration::from_secs(1));

    Sse::new(stream).keep_alive(
        axum::response::sse::KeepAlive::new()
            .interval(Duration::from_secs(1))
            .text("keep-alive-text"),
    )
}
async fn sse_handler_with_id(
    TypedHeader(user_agent): TypedHeader<headers::UserAgent>,
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
    println!("`{}` connected", user_agent.as_str());

    let stream = stream::repeat_with(|| Event::default().data("hi!"))
        .map(Ok)
        .throttle(Duration::from_secs(1));

    Sse::new(stream).keep_alive(
        axum::response::sse::KeepAlive::new()
            .interval(Duration::from_secs(1))
            .text("keep-alive-text"),
    )
}

async fn api_handler() -> String {
    "Hello, lf-api!".to_string()
}

async fn api_handler_with_id() -> String {
    "Hello, lf-api!".to_string()
}
